<template>
  <div class="container">
    <div class="top-title">视频转码</div>
    <div class="top-main">
      <div class="advance-container" v-if="downloading">
        <div class="advance-container-text">正在加载 FFmpeg组件库，请耐心等候...</div>
        <p>{{ downloadingUrl }}</p>
        <el-progress :percentage="downloadingPercentage"></el-progress>
      </div>
      <div class="top-main-left">
        <span style="color: #606266;">*文件将在本地进行处理，使用本页面转码，速度取决于你的电脑性能，</span>
        <span style="color: #ff9f6b;">视频超过2G以上请使用“<a
            href="https://rvzbz5z9a4.jiandaoyun.com/sharedoc/4Dk9C7j0ZMchupSdsGKpcw" target="_blank">格式工厂</a>”转码。</span>
      </div>
      <div class="form">
        <el-form :model="form" label-width="auto" style="max-width: 600px">
          <el-form-item label="清晰度">
            <el-radio-group v-model="form.articulation" @change="changeRadio">
              <el-radio :value="1" border>480P</el-radio>
              <el-radio :value="2" border>720P</el-radio>
              <el-radio :value="3" border>1080P</el-radio>
            </el-radio-group>
          </el-form-item>
          <el-form-item label="视频码率">
            <el-select v-model="form.videoRate" placeholder="请选择视频码率">
              <el-option :label="item" :value="item" v-for="item in videoRateArray" />
            </el-select>
          </el-form-item>
          <el-form-item label="音频码率">
            <el-select v-model="form.voiceRate" placeholder="请选择音频码率">
              <el-option :label="item" :value="item" v-for="item in voiceRateArray" />
            </el-select>
          </el-form-item>
          <el-form-item label="原视频">
            <div class="upload-button">
              <div class="upload">
                <el-upload class="upload-demo" ref="upload" drag :on-change="handleFiles" :on-exceed="handleExceed"
                  :auto-upload="false" :limit="1" :multiple="false">
                  <el-icon class="el-icon--upload"><upload-filled /></el-icon>
                  <div class="el-upload__text">
                    拖转文件或<em>点击此处上传</em>
                  </div>
                  <template #tip>
                    <div class="el-upload__tip">
                      转码文件不应过大 超过2GB，否则转码会失败，超过2GB请使用“格式工厂”转码
                    </div>
                  </template>
                </el-upload>
              </div>
              <div class="button-list">
                <el-button type="info">取消</el-button>
                <el-button type="primary" @click="transcode">开始转码</el-button>
              </div>
            </div>
          </el-form-item>
          <div class="transcoding-info-container" v-if="isTranscoding">
            <div class="transcoding-info-title">转码进度</div>
            <div class="transcoding-info">
              <div class="transcoding-info-item">转码用时： {{ transcodingCurrentTime }}</div>
              <div class="transcoding-info-item">预计还需： {{ transcodingRemainingTime }}</div>
            </div>
            <el-progress :text-inside="true" :stroke-width="26" :percentage="transcodingProgress" />
          </div>
        </el-form>
      </div>

    </div>
  </div>
</template>
   
<script setup>
import { reactive, ref, onMounted } from 'vue'
import { UploadFilled } from '@element-plus/icons-vue'
import 'element-plus/dist/index.css'
import { ElMessage } from 'element-plus'
//ffmpeg 初始化   ffmpeg需要时全局变量
var ffmpeg = null;
//核心库版本控制 
const CORE_VERSION = "0.12.6"
const FFMPEG_VERSION = "0.12.10"
const baseURLFFMPEG = `https://unpkg.com/@ffmpeg/ffmpeg@${FFMPEG_VERSION}/dist/umd`;
const baseURLCore = `https://unpkg.com/@ffmpeg/core@${CORE_VERSION}/dist/umd`;
const baseURLCoreMT = `https://unpkg.com/@ffmpeg/core-mt@${CORE_VERSION}/dist/umd`;
const CORE_SIZE = {
  [`https://unpkg.com/@ffmpeg/core@${CORE_VERSION}/dist/umd/ffmpeg-core.js`]: 114673,
  [`https://unpkg.com/@ffmpeg/core@${CORE_VERSION}/dist/umd/ffmpeg-core.wasm`]: 32129114,
  [`https://unpkg.com/@ffmpeg/core-mt@${CORE_VERSION}/dist/umd/ffmpeg-core.js`]: 132680,
  [`https://unpkg.com/@ffmpeg/core-mt@${CORE_VERSION}/dist/umd/ffmpeg-core.wasm`]: 32609891,
  [`https://unpkg.com/@ffmpeg/core-mt@${CORE_VERSION}/dist/umd/ffmpeg-core.worker.js`]: 2915,
  [`https://unpkg.com/@ffmpeg/ffmpeg@${FFMPEG_VERSION}/dist/umd/814.ffmpeg.js`]: 2648,
};
const videoRateArray = ['256K', '384K', '512K', '768K', '1M']
const voiceRateArray = ['64k', '96k', '128k', '192k']

const form = reactive({
  articulation: 2,
  videoRate: '512K',//视频码率
  voiceRate: '128k',//音频码率
  threadMode: false,//线程模式
  transcodingprogress: 0,//转码进度 
  type: [],
})
//预设参数命令
const ffmpegComdParams = [
  ['-i', 'inputFile', '-c:v', 'libx264', '-vcodec', 'libx264', '-r', '24', '-vf', 'scale=-2:480', '-c:a', 'aac', '-b:a', '128k', '-b:v', '384K', '-f', 'MP4', 'outputName'], //480P
  ['-i', 'inputFile', '-c:v', 'libx264', '-vcodec', 'libx264', '-r', '24', '-vf', 'scale=1280:720', '-c:a', 'aac', '-b:a', '128k', '-b:v', '512K', '-f', 'MP4', 'outputName'], //720P
  ['-i', 'inputFile', '-c:v', 'libx264', '-vcodec', 'libx264', '-r', '24', '-vf', 'scale=1920:1080', '-c:a', 'aac', '-b:a', '128k', '-b:v', '768K', '-f', 'MP4', 'outputName'] //1080P
];
const nowComdParams = ref(ffmpegComdParams[form.articulation - 1])

const transcodingProgress = ref(0)
const transcodingCurrentTime = ref("00:00:00")
const transcodingRemainingTime = ref("00小时00分00秒")

//ffmpeg下载状态
const downloading = ref(false)
const downloadingUrl = ref('')
const downloadingPercentage = ref(0)
const changeRadio = (value) => {
  nowComdParams.value = ffmpegComdParams[value - 1]   //更新页面转码参数
}
//文件上传操作
const targetFile = ref(null)
const handleFiles = (fileObj, fileList) => {
  targetFile.value = fileObj
}
const fileList = ref([]);
const upload = ref(null);
const handleExceed = (files, newFileList) => {
  // 直接设置 fileList 的第一个元素的属性，Vue 3 会自动检测到变化
  fileList.value[0].raw = files[0];
  fileList.value[0].name = files[0].name;
  // 清除上传组件的文件列表
  upload.value?.clearFiles();
  // 处理文件开始上传的逻辑
  upload.value?.handleStart(files[0]);
}

//分片下载组件库 
const toBlobURL = async (url, mimeType, cb) => {
  const resp = await fetch(url);
  let buf;
  if (!resp.ok) {
    throw new Error(`HTTP error! status: ${resp.status}`);
  }
  const total = CORE_SIZE[url];
  let loaded = 0;
  const reader = resp.body.getReader();
  const chunks = [];
  let received = 0;
  for (; ;) {
    const { done, value } = await reader.read();
    const delta = value ? value.length : 0;
    if (done) {
      if (total != -1 && total !== received) throw new Error(`Incompleted download!`);
      cb && cb({ url, total, received, delta, done });
      break;
    }
    chunks.push(value);
    received += delta;
    cb && cb({ url, total, received, delta, done });
  }
  const data = new Uint8Array(received);
  let position = 0;
  for (const chunk of chunks) {
    data.set(chunk, position);
    position += chunk.length;
  }
  const stream = data.buffer;
  const body = await new Response(stream).blob();
  const blob = new Blob([body], { type: mimeType });
  return URL.createObjectURL(blob);
};
const toBlobURLPatched = async (url, mimeType, patcher) => {
  var resp = await fetch(url);
  var body = await resp.text();
  if (patcher) body = patcher(body);
  var blob = new Blob([body], {
    type: mimeType
  });
  return URL.createObjectURL(blob);
};
//下载FFmpeg组件进度
const downloadFFmpeg = (data) => {
  downloadingUrl.value = data.url;
  downloadingPercentage.value = Number(((data.received / data.total) * 100).toFixed(0));
};
//核心组件库加入，首次需要下载
const load = async (threadMode, cb) => {
  const tryMultiThread = threadMode;
  const ffmpegBlobURL = await toBlobURLPatched(`${baseURLFFMPEG}/ffmpeg.js`, 'text/javascript', (js) => js.replace('new URL(e.p+e.u(814),e.b)', 'r.workerLoadURL'));
  await import(ffmpegBlobURL);
  ffmpeg = new FFmpegWASM.FFmpeg();
  ffmpeg.on('log', ({ message }) => {
    // console.log('FFmpeg Log:', message);
  });
  //错误日志打印
  ffmpeg.on('error', (error) => { console.error('FFmpeg Error:', error); });
  if (tryMultiThread || window.crossOriginIsolated) {
    console.log("多线程模式");
    await ffmpeg.load({
      workerLoadURL: await toBlobURL(`${baseURLFFMPEG}/814.ffmpeg.js`, 'text/javascript', cb),
      coreURL: await toBlobURL(`${baseURLCoreMT}/ffmpeg-core.js`, 'text/javascript', cb),
      wasmURL: await toBlobURL(`${baseURLCoreMT}/ffmpeg-core.wasm`, 'application/wasm', cb),
      workerURL: await toBlobURL(`${baseURLCoreMT}/ffmpeg-core.worker.js`, 'application/javascript', cb),
    });
  } else {
    console.log("单线程模式");
    await ffmpeg.load({
      workerLoadURL: await toBlobURL(`${baseURLFFMPEG}/814.ffmpeg.js`, 'text/javascript', cb),
      coreURL: await toBlobURL(`${baseURLCore}/ffmpeg-core.js`, 'text/javascript', cb),
      wasmURL: await toBlobURL(`${baseURLCore}/ffmpeg-core.wasm`, 'application/wasm', cb),
    });
  }
  console.log('ffmpeg 加载成功');
  return ffmpeg;
}

const downloadFileByBlob = (blobUrl, filename) => {
  const eleLink = document.createElement('a')
  eleLink.download = filename
  eleLink.style.display = 'none'
  eleLink.href = blobUrl
  document.body.appendChild(eleLink)
  eleLink.click()
  document.body.removeChild(eleLink)
}
const isTranscoding = ref(false)
const transcode = async () => {
  if (!targetFile.value) {
    ElMessage('请先上传文件')
    return;
  }
  try {
    const inputName = targetFile.value.name;
    const inputFile = `/input/${inputName}`;
    await ffmpeg.createDir('/input');
    await ffmpeg.mount('WORKERFS', { files: [targetFile.value.raw] }, '/input');
    const inputSortName = inputName.split('.')[0];
    const outputName = `${inputSortName}_${Date.now()}.mp4`;
    let tempArgs = [...nowComdParams.value]  // 转码参数
    tempArgs[1] = inputFile
    tempArgs[tempArgs.length - 1] = outputName
    const args = tempArgs
    console.log('开始转码', args);
    isTranscoding.value = true;
    // 记录处理开始的时间
    let startTime = Date.now();
    let usedTime = 0;
    let timer = setInterval(() => {
      usedTime = (Date.now() - startTime) / 1000;
      transcodingCurrentTime.value = convertSecondsToTime(usedTime, 2);
    }, 1000);
    // 监听进度
    ffmpeg.on('progress', ({ progress, time }) => {
      // progress是0-1的浮点数 需进行百分数处理 转成百分数
      transcodingProgress.value = Math.round(progress * 100)
      const averageSpeed = progress * 100 / (time / 1000);
      // 估算剩余时间（单位：秒）
      const remainingTime = Math.floor((1 - progress) / averageSpeed);
      transcodingRemainingTime.value = convertSecondsToTime(remainingTime, 1);
      if (progress == 1) {
        clearInterval(timer);
      }
    })
    //根据参数输出结果
    const result = await ffmpeg.exec(args);
    //结果响应完成
    if (result === 0) {
      const data = await ffmpeg.readFile(outputName);
      downloadFileByBlob(URL.createObjectURL(new Blob([data.buffer])), outputName);
    } else {
      clearInterval(timer);
      ElMessage.error('转码失败');
    }
  } catch (error) {
    console.error('转码错误:', error);
    ElMessage.error('转码发生异常');
  } finally {
    await ffmpeg.unmount('/input');
    await ffmpeg.deleteDir('/input');
  }
}
//用时转化
const convertSecondsToTime = (time, type) => {
  const hours = Math.floor(time / 3600);
  const minutes = Math.floor((time % 3600) / 60);
  const seconds = Math.floor(time % 60);
  if (type == 1) {
    return `${hours.toString().padStart(2, '0')}小时${minutes.toString().padStart(2, '0')}分钟${seconds.toString().padStart(2, '0')}秒`;
  } else {
    return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
  }
}

//页面执行加载及权限
onMounted(async () => {
  downloading.value = true
  //加载组件库
  load(form.threadMode, downloadFFmpeg).then(() => {
    downloading.value = false;
  }).catch((error) => {
    downloading.value = false;
    console.error('Failed to load FFmpeg:', error);
    ElMessage.error('FFmpeg 加载失败，请检查网络或资源地址');
  });
})

</script>
  
<style lang="scss" scoped>
.top-title {
  height: 100px;
  line-height: 100px;
  background-color: #ffffff;
  padding-left: 20px;
}

.top-main {
  box-sizing: border-box;
  margin: 20px;
  height: calc(100vh - 140px);
  background-color: #ffffff;
  padding: 20px;
  box-sizing: border-box;

  .advance-container {
    margin: 30px auto;

    .advance-container-text {
      font-size: 14px;
      color: #606266;
    }
  }

  .form {
    margin-top: 20px;

    .upload-button {
      display: flex;
      flex-direction: column;
      justify-content: center;
    }
  }
 .transcoding-info-title{
  font-size: 14px;
    color: #606266;
 }
  .transcoding-info { 
    font-size: 14px;
    color: #606266;
    display: flex;
    justify-content: space-between;
    margin-bottom: 20px;
  }

  .transcoding-info-item {
    margin-top: 10px;
  }
}
</style>